/*
 * memtester version 4
 *
 * Very simple but very effective user-space memory tester.
 * Originally by Simon Kirby <sim@stormix.com> <sim@neato.org>
 * Version 2 by Charles Cazabon <charlesc-memtester@pyropus.ca>
 * Version 3 not publicly released.
 * Version 4 rewrite:
 * Copyright (C) 2004-2020 Charles Cazabon <charlesc-memtester@pyropus.ca>
 * Licensed under the terms of the GNU General Public License version 2 (only).
 * See the file COPYING for details.
 *
 */

#define __version__ "4.5.1"

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#include "types.h"
#include "sizes.h"
#include "tests.h"
#include "memtester.h"

#define EXIT_FAIL_NONSTARTER    0x01
#define EXIT_FAIL_ADDRESSLINES  0x02
#define EXIT_FAIL_OTHERTEST     0x04

#ifdef __cplusplus
extern "C" {
#endif

static struct test tests[] = {
        // { "Random Value", test_random_value },
        // { "Compare XOR", test_xor_comparison },
        // { "Compare SUB", test_sub_comparison },
        // { "Compare MUL", test_mul_comparison },
        // { "Compare DIV",test_div_comparison },
        // { "Compare OR", test_or_comparison },
        // { "Compare AND", test_and_comparison },
        // { "Sequential Increment", test_seqinc_comparison },
        // { "Solid Bits", test_solidbits_comparison },
        // { "Block Sequential", test_blockseq_comparison },
        // { "Checkerboard", test_checkerboard_comparison },
        // { "Bit Spread", test_bitspread_comparison },
        {"Bit Flip",     test_bitflip_comparison},
        {"Walking Ones", test_walkbits1_comparison},
        // { "Walking Zeroes", test_walkbits0_comparison },
        // #ifdef TEST_NARROW_WRITES
        // { "8-bit Writes", test_8bit_wide_random },
        // { "16-bit Writes", test_16bit_wide_random },
        // #endif
        {NULL, NULL}
};

/* Sanity checks and portability helper macros. */
#ifdef _SC_VERSION

static void check_posix_system(void) {
    if (sysconf(_SC_VERSION) < 198808L) {
        LOGI("A POSIX system is required.  Don't be surprised if this craps out.");
        LOGI("_SC_VERSION is %lu", sysconf(_SC_VERSION));
    }
}

#else
static void  check_posix_system(void)
{}
#endif

#ifdef _SC_PAGE_SIZE

static int memtester_pagesize(void) {
    int pagesize = sysconf(_SC_PAGE_SIZE);
    if (pagesize == -1) {
        LOGE("get page size failed");
        return EXIT_FAIL_NONSTARTER;
    }
    LOGI("pagesize is %ld", (long) pagesize);
    return pagesize;
}

#else
static int memtester_pagesize(void)
{
    LOGE("sysconf(_SC_PAGE_SIZE) not supported; using pagesize of 8192");
    return 8192;
}
#endif

/* Some systems don't define MAP_LOCKED.  Define it to 0 here
   so it's just a no-op when ORed with other constants. */
#ifndef MAP_LOCKED
#define MAP_LOCKED 0
#endif

/* Function definitions */
static void usage() {
    LOGI("Usage options: <mem>[B|K|M|G] [loops]");
}

/*
 * Class:     com_ayst_stresstest_util_MemTester
 * Method:    memtest
 * Signature: ()I
 */
JNIEXPORT jint JNICALL
Java_com_htfyun_factorytest_utils_MemTester_memtest(JNIEnv *env,
                                                                            jclass class,
                                                                            jstring options,
                                                                            jint loops) {
    ul loop, i;
    size_t pagesize, wantraw, wantmb, wantbytes, wantbytes_orig, bufsize, halflen, count;
    char *sizeoption, *memsuffix;
    ptrdiff_t pagesizemask;
    void volatile *buf, *aligned;
    ulv *bufa, *bufb;
    int do_mlock = 0, done_mem = 0;
    int memshift;
    size_t maxbytes = -1; /* addressable memory, in bytes */
    size_t maxmb = (maxbytes >> 20) + 1; /* addressable memory, in MB */
    ul testmask = 0;

//    LOGI("memtester version "
//                 __version__
//                 " (%d-bit)", UL_LEN);
//    LOGI("Copyright (C) 2001-2020 Charles Cazabon.");
//    LOGI("Licensed under the GNU General Public License version 2 (only).");
//    LOGI("");
    check_posix_system();
    pagesize = (size_t) memtester_pagesize();
    pagesizemask = (ptrdiff_t) ~(pagesize - 1);
    LOGI("pagesizemask is 0x%tx", pagesizemask);

    sizeoption = (char *) (*env)->GetStringUTFChars(env, options, JNI_FALSE);
    if (sizeoption == NULL) {
        LOGE("mem size args is NULL!!!");
        return EXIT_FAIL_NONSTARTER;
    }

    wantraw = (size_t) strtoul(sizeoption, &memsuffix, 0);
    if (wantraw == 0) {
        LOGE("failed to parse memory argument");
        usage();
    }
    switch (*memsuffix) {
        case 'G':
        case 'g':
            memshift = 30; /* gigabytes */
            break;
        case 'M':
        case 'm':
            memshift = 20; /* megabytes */
            break;
        case 'K':
        case 'k':
            memshift = 10; /* kilobytes */
            break;
        case 'B':
        case 'b':
            memshift = 0; /* bytes*/
            break;
        case '\0':  /* no suffix */
            memshift = 20; /* megabytes */
            break;
        default:
            usage();
    }
    wantbytes_orig = wantbytes = ((size_t) wantraw << memshift);
    wantmb = (wantbytes_orig >> 20);

    if (wantmb > maxmb) {
        LOGE("This system can only address %llu MB.", (ull) maxmb);
        return EXIT_FAIL_NONSTARTER;
    }
    if (wantbytes < pagesize) {
        LOGE("bytes %ld < pagesize %ld -- memory argument too large?", wantbytes, pagesize);
        return EXIT_FAIL_NONSTARTER;
    }

    LOGI("want %lluMB (%llu bytes)", (ull) wantmb, (ull) wantbytes);
    buf = NULL;

    while (!done_mem) {
        while (!buf && wantbytes) {
            buf = (void volatile *) malloc(wantbytes);
            if (!buf) {
                wantbytes -= pagesize;
            }
        }
        bufsize = wantbytes;
        LOGI("got  %lluMB (%llu bytes)", (ull) wantbytes >> 20, (ull) wantbytes);
        if (do_mlock) {
            LOGI("trying mlock ...");
            if ((size_t) buf % pagesize) {
                LOGI("aligning to page -- was 0x%tx", buf);
                aligned = (void volatile *) ((size_t) buf & pagesizemask) + pagesize;
                LOGI("  now 0x%tx -- lost %d bytes", aligned, (size_t) aligned - (size_t) buf);
                bufsize -= ((size_t) aligned - (size_t) buf);
            } else {
                aligned = buf;
            }
            /* Try mlock */
            if (mlock((void *) aligned, bufsize) < 0) {
                switch (errno) {
                    case EAGAIN: /* BSDs */
                        LOGI("over system/pre-process limit, reducing...");
                        free((void *) buf);
                        buf = NULL;
                        wantbytes -= pagesize;
                        break;
                    case ENOMEM:
                        LOGI("too many pages, reducing...");
                        free((void *) buf);
                        buf = NULL;
                        wantbytes -= pagesize;
                        break;
                    case EPERM:
                        LOGI("insufficient permission.");
                        LOGI("Trying again, unlocked:");
                        do_mlock = 0;
                        free((void *) buf);
                        buf = NULL;
                        wantbytes = wantbytes_orig;
                        break;
                    default:
                        LOGI("failed for unknown reason.");
                        do_mlock = 0;
                        done_mem = 1;
                }
            } else {
                LOGI("locked.");
                done_mem = 1;
            }
        } else {
            done_mem = 1;
            LOGI("\n");
        }
    }

    if (!do_mlock) {
//        LOGE("Continuing with unlocked memory; testing will be slower and less reliable.");
    }

    /* Do alighnment here as well, as some cases won't trigger above if you
       define out the use of mlock() (cough HP/UX 10 cough). */
    if ((size_t) buf % pagesize) {
        LOGI("aligning to page -- was 0x%tx", buf);
        aligned = (void volatile *) ((size_t) buf & pagesizemask) + pagesize;
        LOGI("  now 0x%tx -- lost %d bytes", aligned, (size_t) aligned - (size_t) buf);
        bufsize -= ((size_t) aligned - (size_t) buf);
    } else {
        aligned = buf;
    }

    halflen = bufsize / 2;
    count = halflen / sizeof(ul);
    bufa = (ulv *) aligned;
    bufb = (ulv *) ((size_t) aligned + halflen);

    for (loop = 1; ((!loops) || loop <= loops); loop++) {
        LOGI("Loop %lu /%lu : Stuck Address", loop, loops);
        if (!test_stuck_address(aligned, bufsize / sizeof(ul))) {
            LOGI("  Stuck ok.");
        } else {
            LOGE("  Stuck fail.");
            return EXIT_FAIL_ADDRESSLINES;
        }
        for (i = 0;; i++) {
            if (!tests[i].name) {
                break;
            }
            /* If using a custom testmask, only run this test if the
               bit corresponding to this test was set by the user.
             */
            if (testmask && (!((1 << i) & testmask))) {
                continue;
            }
            LOGI("Loop %lu /%lu : %s", loop, loops, tests[i].name);
            if (!tests[i].fp(bufa, bufb, count)) {
                LOGI("  %s ok.", tests[i].name);
            } else {
                LOGE("  %s fail.", tests[i].name);
                return EXIT_FAIL_OTHERTEST;
            }
            /* clear buffer */
            memset((void *) buf, 255, wantbytes);
        }
    }
    if (do_mlock) {
        munlock((void *) aligned, bufsize);
    }
    if (buf) {
        free((void *) buf);
    }
    LOGI("Done.");
    return 0;
}

#ifdef __cplusplus
}
#endif
